package org.bhf.security.authorization;

import java.io.Serializable;
import java.security.AllPermission;
import java.security.Permission;
import java.security.PermissionCollection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.NoSuchElementException;

/**
 * This class is a replacement for <code>java.lang.security.Permissions</code> for use within
 * the Security II framework. Specifically, the following modifications were made.
 * <ul>
 *  <li>Support for unresolvable permissions is removed. Providing this support easily requires
 *      package level access to classes with <code>java.lang.security</code></li>
 *  <li><code>Permissions.elements( Permissions )</code> is made public to support enumerating
 *      a specific class of permissions.</li>
 *  <li>The implementation allows for lazy loading and selective caching of permissions
 *      by class as a future optimization.</li>
 * </ul>
 *
 * @see java.security.Permissions
 * @see java.security.Permission
 * @see java.security.PermissionCollection
 * @see java.security.AllPermission
 *
 * @serial exclude
 */

public final class Permissions extends PermissionCollection
    implements Serializable
{
    private static final long serialVersionUID = 7930732926638008763L;

    final private Hashtable<Class<? extends Permission>,PermissionCollection> perms;

    // optimization. keep track of the AllPermission collection
    private PermissionCollection allPermission;

    /**
     * Creates a new Permissions object containing no PermissionCollections.
     */
    public Permissions()
    {
        perms = new Hashtable<Class<? extends Permission>,PermissionCollection>( 11 );
        allPermission = null;
    }

    /**
     * Adds a permission object to the PermissionCollection for the class the
     * permission belongs to. For example, if <i>permission</i> is a
     * FilePermission, it is added to the FilePermissionCollection stored
     * in this Permissions object.
     *
     * This method creates
     * a new PermissionCollection object (and adds the permission to it)
     * if an appropriate collection does not yet exist. <p>
     *
     * @param permission the Permission object to add.
     *
     * @exception SecurityException if this Permissions object is
     * marked as readonly.
     *
     * @see java.security.PermissionCollection#isReadOnly()
     */

    public void                 add( Permission permission )
    {
        if( isReadOnly() )
            throw new SecurityException( "attempt to add a Permission to a readonly Permissions object" );
        PermissionCollection pc = getPermissionCollection( permission );
        pc.add( permission );
        if( permission instanceof AllPermission )
        {
            allPermission = pc;
        }
    }

    /**
     * Checks to see if this object's PermissionCollection for permissions of
     * the specified permission's type implies the permissions
     * expressed in the <i>permission</i> object. Returns true if the
     * combination of permissions in the appropriate PermissionCollection
     * (e.g., a FilePermissionCollection for a FilePermission) together
     * imply the specified permission.
     *
     * <p>For example, suppose there is a FilePermissionCollection in this
     * Permissions object, and it contains one FilePermission that specifies
     * "read" access for  all files in all subdirectories of the "/tmp"
     * directory, and another FilePermission that specifies "write" access
     * for all files in the "/tmp/scratch/foo" directory.
     * Then if the <code>implies</code> method
     * is called with a permission specifying both "read" and "write" access
     * to files in the "/tmp/scratch/foo" directory, <code>true</code> is
     * returned.
     *
     * <p>Additionally, if this PermissionCollection contains the
     * AllPermission, this method will always return true.
     * <p>
     * @param permission the Permission object to check.
     *
     * @return true if "permission" is implied by the permissions in the
     * PermissionCollection it
     * belongs to, false if not.
     */

    public boolean              implies( Permission permission )
    {
        if( allPermission != null && allPermission.implies( permission ) )
        {
            return true;
        }
        else
        {
            PermissionCollection pc = getPermissionCollection( permission );
            return pc.implies( permission );
        }
    }

    /**
     * Returns an enumeration of all the Permission objects in all the
     * PermissionCollections in this Permissions object.
     *
     * @return an enumeration of all the Permissions.
     */
    public Enumeration<Permission>  elements()
    {
        // go through each Permissions in the hash table
        // and call their elements() function.
        return new PermissionsEnumerator( perms.elements() );
    }

    /**
     * Returns an enumeration of all the Permission objects with the same
     * type as <i>p</i>.
     *
     * @param p the prototype Permission object.
     *
     * @return an enumeration of all the Permissions with the
     * same type as <i>p</i>.
     */
    public Enumeration          elements( final Permission p )
    {
        PermissionCollection pc = getPermissionCollection( p );
        return pc.elements();
    }

    /**
     * Gets the PermissionCollection in this Permissions object for
     * permissions whose type is the same as that of <i>p</i>.
     * For example, if <i>p</i> is a FilePermission,
     * the FilePermissionCollection
     * stored in this Permissions object will be returned.
     *
     * This method creates a new PermissionCollection object for the specified
     * type of permission objects if one does not yet exist.
     * To do so, it first calls the <code>newPermissionCollection</code> method
     * on <i>p</i>.  Subclasses of class Permission
     * override that method if they need to store their permissions in a
     * particular PermissionCollection object in order to provide the
     * correct semantics when the <code>PermissionCollection.implies</code>
     * method is called.
     * If the call returns a PermissionCollection, that collection is stored
     * in this Permissions object. If the call returns null, then
     * this method instantiates and stores a default PermissionCollection
     * that uses a hashtable to store its permission objects.
     *
     * @param p The object defining the <code>Class</code> of <code>Permission</code> to retrieve.
     * @return Will not be <code>null</code>
     */
    public PermissionCollection getPermissionCollection( final Permission p )
    {
        Class<? extends Permission> c = p.getClass();
        PermissionCollection pc;

        synchronized( perms )
        {
            pc = perms.get( c );

            //check for unresolved permissions
            if( pc == null )
            {

                pc = p.newPermissionCollection();

                // still no PermissionCollection?
                // We'll give them a PermissionsHash.
                if( pc == null )
                    pc = new PermissionsHash();

                perms.put( c, pc );
            }
        }
        return pc;
    }
}

final class PermissionsEnumerator implements Enumeration<Permission>
{

    // all the perms
    private Enumeration<PermissionCollection> perms;
    // the current set
    private Enumeration<Permission> permset;

    PermissionsEnumerator( Enumeration<PermissionCollection> e )
    {
        perms = e;
        permset = getNextEnumWithMore();
    }

    public synchronized boolean hasMoreElements()
    {
        // if we enter with permissionimpl null, we know
        // there are no more left.
        if( permset == null )
            return false;

        // try to see if there are any left in the current one
        if( permset.hasMoreElements() )
            return true;

        // getValue the next one that has something in it...
        permset = getNextEnumWithMore();

        // if it is null, we are done!
        return (permset != null);
    }

    public synchronized Permission nextElement()
    {

        // hasMoreElements will update permset to the next permset
        // with something in it...

        if( hasMoreElements() )
        {
            return permset.nextElement();
        }
        else
        {
            throw new NoSuchElementException( "PermissionsEnumerator" );
        }

    }

    private Enumeration<Permission> getNextEnumWithMore()
    {
        while( perms.hasMoreElements() )
        {
            PermissionCollection pc = perms.nextElement();
            Enumeration<Permission> next = pc.elements();
            if( next.hasMoreElements() )
                return next;
        }
        return null;
    }
}

/**
 * A PermissionsHash stores a homogeneous set of permissions in a hashtable.
 *
 * @see java.security.Permission
 * @see java.security.Permissions
 *
 * @version 1.50, 02/01/02
 *
 * @author Roland Schemers
 *
 * @serial include
 */

final class PermissionsHash extends PermissionCollection
    implements Serializable
{
    private static final long serialVersionUID = 7930732926638008763L;

    private Hashtable<Permission,Permission> perms;

    /**
     * Create an empty PermissionsHash object.
     */

    PermissionsHash()
    {
        perms = new Hashtable<Permission,Permission>( 11 );
    }

    /**
     * Adds a permission to the PermissionsHash.
     *
     * @param permission the Permission object to add.
     */

    public void add( final Permission permission )
    {
        perms.put( permission, permission );
    }

    /**
     * Check and see if this set of permissions implies the permissions
     * expressed in "permission".
     *
     * @param permission the Permission object to compare
     *
     * @return true if "permission" is a proper subset of a permission in
     * the set, false if not.
     */

    public boolean implies( final Permission permission )
    {
        // attempt a fast lookup and implies. If that fails
        // then enumerate through all the permissions.
        Permission p = perms.get( permission );
        if( (p == null) || (!p.implies( permission )) )
        {
            Enumeration<Permission> e = elements();
            try
            {
                while( e.hasMoreElements() )
                {
                    p = e.nextElement();
                    if( p.implies( permission ) )
                        return true;
                }
            }
            catch( NoSuchElementException nsee )
            {
                // ignore
            }
            return false;
        }
        else
        {
            return true;
        }
    }

    /**
     * Returns an enumeration of all the Permission objects in the container.
     *
     * @return an enumeration of all the Permissions.
     */
    public Enumeration<Permission> elements()
    {
        return perms.elements();
    }
}




